# Sketch - A Python-based interactive drawing program
# Copyright (C) 1996, 1997, 1998, 1999 by Bernhard Herzog
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307	USA

#
#       The layer classes. 
#

from types import TupleType
from Sketch.const import LAYER_STATE, LAYER_COLOR

from Sketch import _, NullUndo, EmptyRect, InfinityRect, Point, config, _sketch

import color
import selinfo

from compound import EditableCompound

class Layer(EditableCompound):

    can_be_empty = 1
    is_Layer = 1
    is_SpecialLayer = 0
    is_GridLayer = 0
    is_GuideLayer = 0

    def __init__(self, name = _("New Layer"),
                 visible = 1, printable = 1, locked = 0,
                 outlined = 0, outline_color = (0, 0, 0)):
        EditableCompound.__init__(self, [])
        self.name = name
        self.visible = visible
        self.printable = printable
        self.locked = locked
        self.outlined = outlined
        if type(outline_color) == TupleType:
            self.outline_color = apply(color.CreateRGBColor, outline_color)
        else:
            self.outline_color = outline_color

    def Draw(self, device, rect = None):
        # Draw all objects on the device device. RECT, if provided,
        # gives the bounding rect of the region to be drawn allowing to
        # optimize the redisplay by drawing only those objects that
        # overlap with this rect.
        if device.draw_visible and self.visible \
           or device.draw_printable and self.printable:
            outlined = self.outlined or device.IsOutlineActive()
            if outlined:
                device.StartOutlineMode(self.outline_color)
            EditableCompound.DrawShape(self, device, rect)
            if outlined:
                device.EndOutlineMode()

    def SelectSubobject(self, p, rect, device, path = (), *rest):
        if not self.CanSelect():
            return None
        if self.outlined:
            device.StartOutlineMode()
        try:
            result = EditableCompound.SelectSubobject(self, p, rect, device,
                                                      path)
        finally:
            if self.outlined:
                device.EndOutlineMode()

        return result

    def SelectRect(self, rect):
        if not self.CanSelect():
            return []
        test = rect.contains_rect
        build_info = selinfo.build_info
        selected = []
        objects = self.objects
        for idx in range(len(objects)):
            obj = objects[idx]
            if test(obj.bounding_rect):
                selected.append(build_info(idx, obj))
        return selected

    def SelectAll(self):
        if self.CanSelect():
            return selinfo.select_all(self.objects)
        else:
            return []

    def SelectionInfo(self, child, cache = None):
        info = selinfo.build_info(_sketch.IdIndex(self.objects, child), child)
        return selinfo.prepend_idx(self.document.LayerIndex(self), info)

    def PickObject(self, p, rect, device):
        if not self.visible:
            return None
        if self.outlined:
            device.StartOutlineMode()
        result = EditableCompound.PickObject(self, p, rect, device)
        if self.outlined:
            device.EndOutlineMode()
        return result

    def SetName(self, name):
        undo = (self.SetName, self.name)
        self.name = name
        return undo

    def Name(self):
        return self.name

    def NumObjects(self):
        return len(self.objects)

    def Visible(self):
        return self.visible

    def Printable(self):
        return self.printable

    def Locked(self):
        return self.locked

    def CanSelect(self):
        return not self.locked and self.visible

    def get_state(self):
        return (self.visible, self.printable, self.locked, self.outlined)

    def SetState(self, visible, printable, locked, outlined):
        # set the layer state. Return undo info.
        #
        # Side Effect:
        # Queue a LAYER message with parameter LAYER_STATE + tuple.
        # The tuple has the form
        #
        # (layer, visible_changed, printable_changed, outline_changed)
        #
        # We assume here that the receiver (usually SketchCanvas or
        # SketchView) uses this to determine whether to repaint parts of
        # the screen. If the receiver shows only visible layers and
        # allows outline, it should use the following expression to
        # determine whether to redraw or not:
        #
        #    layer.NumObjects() and (visible_changed or
        #				(outlined_changed and layer.Visible()))
        #
        # If you only show printable layers:
        #
        #    layer.NumObjects() and printable_changed
        #
        # (in that case outline mode should be ignored as it is only
        # meant for quicker or clearer display while editing)
        #
        # The bounding rect of the now invalid region is
        # layer.bounding_rect

        oldstate = self.get_state()
        visible_changed = self.visible != visible
        self.visible = visible
        printable_changed = self.printable != printable
        self.printable = printable
        locked_changed = self.locked != locked
        self.locked = locked
        outlined_changed = self.outlined != outlined
        self.outlined = outlined

        if oldstate != self.get_state():
            undo = (self.SetState,) + oldstate
            visibility = (self, visible_changed, printable_changed,
                          outlined_changed)
            if self.document is not None:
                self.document.queue_layer(LAYER_STATE, visibility)
            if locked_changed:
                self.document.update_active_layer()
            return undo

        return NullUndo

    def SetOutlineColor(self, color):
        undo = (self.SetOutlineColor, self.outline_color)
        self.outline_color = color
        if self.document is not None:
            self.document.queue_layer(LAYER_COLOR, self)
        return undo

    def OutlineColor(self):
        return self.outline_color

    def Outlined(self):
        return self.outlined

    def SaveToFile(self, file):
        file.BeginLayer(self.name, self.visible, self.printable, self.locked,
                        self.outlined, self.outline_color)
        for obj in self.objects:
            obj.SaveToFile(file)
        file.EndLayer()

class SpecialLayer(Layer):

    is_SpecialLayer = 1

    def __none(self, *args):
        return None

    SelectSubobject = __none
    PickObject = __none

    def SelectRect(self, *rect):
        return []

    SelectAll = SelectRect


class GuideLayer(SpecialLayer):

    is_GuideLayer = 1

    def __init__(self, name = _("Guides"), visible = 1, printable = 0,
                 locked = 0, outlined = 1, outline_color = None):
        if outline_color is None:
            outline_color = config.preferences.guide_color
        SpecialLayer.__init__(self, name, visible, 0, locked, 1,
                              outline_color)

    def SetState(self, visible, printable, locked, outlined):
        return SpecialLayer.SetState(self, visible, 0, locked, outlined)

    def Draw(self, device, rect = None):
        if device.draw_visible and self.visible \
           or device.draw_printable and self.printable:
            device.StartOutlineMode(self.outline_color)
            SpecialLayer.DrawShape(self, device)
            device.EndOutlineMode()

    def SelectSubobject(self, p, rect, device, path = (), *rest):
        if not self.CanSelect():
            return None
        device.StartOutlineMode()
        try:
            objects = self.objects
            for obj_idx in range(len(objects) - 1, -1, -1):
                obj = objects[obj_idx]
                if obj.Hit(p, rect, device):
                    result = obj.SelectSubobject(p, rect, device)
                    return selinfo.prepend_idx(obj_idx, result)
            return None
        finally:
            device.EndOutlineMode()

    def SelectRect(self, rect):
        if not self.CanSelect():
            return []
        test = rect.contains_rect
        build_info = selinfo.build_info
        selected = []
        objects = self.objects
        for idx in range(len(objects)):
            obj = objects[idx]
            if not obj.is_GuideLine and test(obj.bounding_rect):
                selected.append(build_info(idx, obj))
        return selected

    def SelectAll(self):
        return self.SelectRect(InfinityRect)

    def compute_rects(self):
        if self.objects:
            self.bounding_rect = self.coord_rect = InfinityRect
        else:
            self.bounding_rect = self.coord_rect = EmptyRect

    def SaveToFile(self, file):
        file.BeginGuideLayer(self.name, self.visible, self.printable,
                             self.locked, self.outlined, self.outline_color)
        for obj in self.objects:
            obj.SaveToFile(file)
        file.EndGuideLayer()

    def Snap(self, p):
        default = (1e100, p)
        horizontal = [default]
        vertical = [default]
        result = [default]
        for obj in self.objects:
            dist, snapped = obj.Snap(p)
            if type(snapped) == TupleType:
                if snapped[0] is None:
                    horizontal.append((dist, snapped))
                else:
                    vertical.append((dist, snapped))
            else:
                result.append((dist, snapped))

        return min(horizontal), min(vertical), min(result)

    def GuideLines(self):
        result = self.objects[:]
        for idx in range(len(result) - 1, -1, -1):
            if not result[idx].is_GuideLine:
                del result[idx]
        return result

    def issue_changed(self):
        Layer.issue_changed(self)
        if self.document is not None:
            self.document.GuideLayerChanged(self)

class GridLayer(SpecialLayer):

    is_GridLayer = 1
    geometry = (0, 0, 20, 20)

    def __init__(self, geometry = None, visible = None, outline_color = None,
                 name = _("Grid")):
        # The grid is locked, outlined and not printable
        if geometry is None:
            geometry = config.preferences.grid_geometry
        if visible is None:
            visible = config.preferences.grid_visible
        if outline_color is None:
            outline_color = config.preferences.grid_color
        SpecialLayer.__init__(self, name, visible, 0, 1, 1, outline_color)
        if len(geometry) == 2:
            self.geometry = (0, 0) + geometry
        elif len(geometry) == 4:
            self.geometry = geometry
        else:
            raise ValueError, "grid tuple must have length 2 or 4"

    def Draw(self, device, rect = None):
        if device.draw_visible and self.visible \
           or device.draw_printable and self.printable:
            device.StartOutlineMode(self.outline_color)
            xorg, yorg, xwidth, ywidth = self.geometry
            device.DrawGrid(xorg, yorg, xwidth, ywidth, rect)
            device.EndOutlineMode()

    def update_rects(self):
        self.bounding_rect = self.coord_rect = InfinityRect

    def SaveToFile(self, file):
        file.BeginGridLayer(self.geometry, self.visible, self.outline_color,
                            self.name)
        file.EndGridLayer()

    def Snap(self, p):
        xorg, yorg, xwidth, ywidth = self.geometry
        sx = round((p.x - xorg) / xwidth) * xwidth + xorg
        sy = round((p.y - yorg) / ywidth) * ywidth + yorg
        result = Point(sx, sy)
        return (abs(result - p), result)

    def SetState(self, visible, printable, locked, outlined):
        return SpecialLayer.SetState(self, visible, 0, 1, 1)

    def Geometry(self):
        return self.geometry

    def SetGeometry(self, geometry):
        undo = (self.SetGeometry, self.geometry)
        self.geometry = geometry
        if self.document:
            # a hack...
            self.document.queue_layer(LAYER_COLOR, self)
        return undo

